﻿
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Sykj.CodeGenerator
{
    /// <summary>
    /// 代码生成器  
    /// 参考 https://www.cnblogs.com/yilezhu
    /// </summary>
    public class CodeGenerator
    {
        private readonly string Delimiter = "\\";//分隔符，默认为windows下的\\分隔符

        private static CodeGenerateOption _options;

        public CodeGenerator(CodeGenerateOption options)
        {
            _options = options;
            var path = AppDomain.CurrentDomain.BaseDirectory;
            _options.OutputPath = path;
            var flag = path.IndexOf("/bin");
            if (flag > 0)
                Delimiter = "/";//如果可以取到值，修改分割符
        }

        /// <summary>
        /// 根据数据库连接字符串生成数据库表对应的模板代码
        /// </summary>
        /// <param name="isCoveredExsited">是否覆盖已存在的同名文件</param>
        public void GenerateTemplateCodesFromDatabase(bool isCoveredExsited = true)
        {
            Console.WriteLine("正在读取数据库信息...");

            DbEntity dbEntity = new DbEntity(_options.DbType, _options.ConnectionString);
            List<DbTable> tables = dbEntity.GetCurrentDatabaseTableList();

            Console.WriteLine("数据库信息读取完成，正在生成文件...");

            if (tables != null && tables.Any())
            {
                foreach (var table in tables)
                {  
                    GenerateEntity(table, isCoveredExsited);

                    GenerateIServices(table, isCoveredExsited);

                    GenerateServices(table, isCoveredExsited);

                    GenerateMap(table, isCoveredExsited);

                    GenerateDbContext(dbEntity.DataBaseName, tables, isCoveredExsited);

                    GenerateDataService(tables, isCoveredExsited);

                    GenerateControllers(table, isCoveredExsited);

                    GenerateAddView(table, isCoveredExsited);

                    GenerateEditView(table, isCoveredExsited);

                    GenerateListView(table, isCoveredExsited);
                }
            }
        }

        #region 生成各模块私有方法

        #region 视图代码

        /// <summary>
        /// 生成ListView码文件
        /// </summary>
        /// <param name="table"></param>
        /// <param name="ifExsitedCovered"></param>
        private void GenerateListView(DbTable table, bool ifExsitedCovered = true)
        {
            var path = _options.OutputPath + Delimiter + "Views" + Delimiter + GetFileName(table.TableName);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            var fullPath = path + Delimiter + "Index.cshtml";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;

            //获取主键的字段名
            string primaryKeyName = string.Empty;
            if (table.Columns.Any(c => c.IsPrimaryKey))
            {
                primaryKeyName = table.Columns.First(m => m.IsPrimaryKey).ColName;
            }

            var sb = new StringBuilder();
            foreach (var column in table.Columns)
            {
                sb.AppendLine("\t\t<th lay-data=\"{ field: '"+ column.ColName + "'}\">"+column.Comment+"</th>");
            }

            var content = ReadTemplate("ListViewTemplate.txt");
            content = content.Replace("{Comment}", table.TableComment)
                .Replace("{ColumnList}", sb.ToString())
                .Replace("{PrimaryKey}", UpperFirstChar(primaryKeyName))
                .Replace("{ModelName}", GetFileName(table.TableName));

            WriteAndSave(fullPath, content);

            Console.WriteLine("生成" + table.TableName + "ListView!");
        }

        /// <summary>
        /// 生成EditView码文件
        /// </summary>
        /// <param name="table"></param>
        /// <param name="ifExsitedCovered"></param>
        private void GenerateEditView(DbTable table, bool ifExsitedCovered = true)
        {
            var path = _options.OutputPath + Delimiter + "Views" + Delimiter + GetFileName(table.TableName);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            var fullPath = path + Delimiter + "Edit.cshtml";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;

            var sb = new StringBuilder();
            foreach (var column in table.Columns)
            {
                sb.AppendLine("\t\t<div class=\"layui-form-item\"> ");
                sb.AppendLine("\t\t   <label class=\"layui-form-label\">" + column.Comment + "</label>");
                sb.AppendLine("\t\t      <div class=\"layui-input-block\">");
                sb.AppendLine("\t\t        <input type=\"text\" asp-for=\"" + UpperFirstChar(column.ColName) + "\" lay-verify=\"required\" class=\"layui-input\">");
                sb.AppendLine("\t\t      </div> ");
                sb.AppendLine("\t\t</div>");
            }

            var content = ReadTemplate("EditViewTemplate.txt");
            content = content.Replace("{Comment}", table.TableComment)
                .Replace("{FromList}", sb.ToString())
                .Replace("{ModelName}", GetFileName(table.TableName));

            WriteAndSave(fullPath, content);

            Console.WriteLine("生成" + table.TableName + "EditView!");
        }

        /// <summary>
        /// 生成AddView码文件
        /// </summary>
        /// <param name="table"></param>
        /// <param name="ifExsitedCovered"></param>
        private void GenerateAddView(DbTable table, bool ifExsitedCovered = true)
        {
            var path = _options.OutputPath + Delimiter + "Views" + Delimiter + GetFileName(table.TableName);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            var fullPath = path + Delimiter + "Add.cshtml";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;

            var sb = new StringBuilder();
            foreach (var column in table.Columns)
            {
                sb.AppendLine("\t\t<div class=\"layui-form-item\"> ");
                sb.AppendLine("\t\t   <label class=\"layui-form-label\">" + column.Comment + "</label>");
                sb.AppendLine("\t\t      <div class=\"layui-input-block\">");
                sb.AppendLine("\t\t        <input type=\"text\" name=\"" +UpperFirstChar(column.ColName)+"\" lay-verify=\"required\" class=\"layui-input\">");
                sb.AppendLine("\t\t      </div> ");
                sb.AppendLine("\t\t</div>");
            }

            var content = ReadTemplate("AddViewTemplate.txt");
            content = content.Replace("{Comment}", table.TableComment)
                .Replace("{FromList}", sb.ToString())
                .Replace("{ModelName}", GetFileName(table.TableName));

            WriteAndSave(fullPath, content);

            Console.WriteLine("生成" + table.TableName + "AddView!");
        }

        #endregion

        #region 控制器

        /// <summary>
        /// 生成Controllers层代码文件
        /// </summary>
        /// <param name="table"></param>
        /// <param name="ifExsitedCovered"></param>
        private void GenerateControllers(DbTable table, bool ifExsitedCovered = true)
        {
            var path = _options.OutputPath + Delimiter + "Controllers";
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            var fullPath = path + Delimiter + GetFileName(table.TableName) + "Controller.cs";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;

            //获取主键的字段名
            string primaryKeyName = string.Empty;
            if (table.Columns.Any(c => c.IsPrimaryKey))
            {
                primaryKeyName = table.Columns.First(m => m.IsPrimaryKey).ColName;
            }

            var content = ReadTemplate("ControllerTemplate.txt");
            content = content.Replace("{Comment}", table.TableComment)
                .Replace("{Author}", _options.Author)
                .Replace("{GeneratorTime}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                .Replace("{ModelName}", GetFileName(table.TableName))
                .Replace("{PrimaryKey}", UpperFirstChar(primaryKeyName))
                .Replace("{ModelNamexx}", GetFileName(table.TableName).ToLower());
            WriteAndSave(fullPath, content);

            Console.WriteLine("生成" + table.TableName + "Controller!");
        }

        #endregion

        #region DataServiceExtension&DbContext

        /// <summary>
        /// 生成DataServiceExtension
        /// </summary>
        /// <param name="table">表名</param>
        /// <param name="isCoveredExsited">是否覆盖</param>
        private void GenerateDataService( List<DbTable> tables, bool isCoveredExsited = true)
        {
            var modelPath = _options.OutputPath + "Web";
            if (!Directory.Exists(modelPath))
            {
                Directory.CreateDirectory(modelPath);
            }

            //var fullPath = modelPath + UpperFirstChar(dataBaseName) + "DbContext.cs";
            var fullPath = modelPath + Delimiter + "DataServiceExtension.cs";
            if (File.Exists(fullPath) && !isCoveredExsited)
                return;

            var sb = new StringBuilder();
            foreach (var table in tables)
            {
                sb.AppendLine("\t\tservices.AddScoped<" + _options.IServicesNamespace + ".I" + GetFileName(table.TableName) + ", " + _options.ServicesNamespace + "." + GetFileName(table.TableName) + ">();");
            }

            var content = ReadTemplate("DataServiceExtensionTemplate.txt");
            content = content.Replace("{GeneratorTime}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                .Replace("{Author}", _options.Author)
                .Replace("{DataServiceList}", sb.ToString());

            WriteAndSave(fullPath, content);

            Console.WriteLine("生成DataServiceExtension!");
        }

        /// <summary>
        /// 生成DbContext
        /// </summary>
        /// <param name="dataBaseName">数据库名称</param>
        /// <param name="table">表名</param>
        /// <param name="isCoveredExsited">是否覆盖</param>
        private void GenerateDbContext(string dataBaseName,List<DbTable> tables, bool isCoveredExsited = true)
        {
            var modelPath = _options.OutputPath + "Web";
            if (!Directory.Exists(modelPath))
            {
                Directory.CreateDirectory(modelPath);
            }

            //var fullPath = modelPath + UpperFirstChar(dataBaseName) + "DbContext.cs";
            var fullPath = modelPath + Delimiter + "SyDbContext.cs";
            if (File.Exists(fullPath) && !isCoveredExsited)
                return;

            var sbDbSet = new StringBuilder();
            var sbMap = new StringBuilder();
            foreach (var table in tables)
            {
                sbDbSet.AppendLine("\t\tpublic virtual DbSet<" + _options.ModelsNamespace + "." + GetFileName(table.TableName) + "> " + GetFileName(table.TableName) + " { get; set; }");
                sbMap.AppendLine("\t\tmodelBuilder.ApplyConfiguration(new " + GetFileName(table.TableName) + "Map());");
            }

            var content = ReadTemplate("DbContextTemplate.txt");
            content = content.Replace("{GeneratorTime}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                .Replace("{Author}", _options.Author)
                .Replace("{DbSetList}", sbDbSet.ToString())
                .Replace("{MapList}", sbMap.ToString());

            WriteAndSave(fullPath, content);

            Console.WriteLine("生成SyDbContext!");
        }

        #endregion

        #region 实体

        /// <summary>
        /// 生成实体代码
        /// </summary>
        /// <param name="table">表名</param>
        /// <param name="isCoveredExsited">是否覆盖</param>
        private void GenerateEntity(DbTable table, bool isCoveredExsited = true)
        {
            var modelPath = _options.OutputPath + Delimiter + "Entity";
            if (!Directory.Exists(modelPath))
            {
                Directory.CreateDirectory(modelPath);
            }

            var fullPath = modelPath + Delimiter + GetFileName(table.TableName) + ".cs";
            if (File.Exists(fullPath) && !isCoveredExsited)
                return;

            var sb = new StringBuilder();
            foreach (var column in table.Columns)
            {
                var tmp = GenerateEntityProperty(column);
                sb.AppendLine(tmp);
            }
            var content = ReadTemplate("EntityTemplate.txt");
            content = content.Replace("{GeneratorTime}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                .Replace("{ModelsNamespace}", _options.ModelsNamespace)
                .Replace("{Author}", _options.Author)
                .Replace("{Comment}", table.TableComment)
                .Replace("{ModelName}", GetFileName(table.TableName))
                .Replace("{ModelProperties}", sb.ToString());
            WriteAndSave(fullPath, content);

            Console.WriteLine("生成"+ table.TableName + "Entity!");
        }

        /// <summary>
        /// 生成实体映射代码
        /// </summary>
        /// <param name="table">表名</param>
        /// <param name="isCoveredExsited">是否覆盖</param>
        private void GenerateMap(DbTable table, bool isCoveredExsited = true)
        {
            var modelPath = _options.OutputPath + Delimiter + "Map";
            if (!Directory.Exists(modelPath))
            {
                Directory.CreateDirectory(modelPath);
            }

            var fullPath = modelPath + Delimiter + GetFileName(table.TableName) + "Map.cs";
            if (File.Exists(fullPath) && !isCoveredExsited)
                return;

            //获取主键的字段名
            string primaryKeyName = string.Empty;
            if (table.Columns.Any(c => c.IsPrimaryKey))
            {
                primaryKeyName = table.Columns.First(m => m.IsPrimaryKey).ColName;
            }

            var content = ReadTemplate("MapTemplate.txt");
            content = content.Replace("{GeneratorTime}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                .Replace("{Author}", _options.Author)
                .Replace("{Comment}", table.TableComment)
                .Replace("{ModelName}", GetFileName(table.TableName))
                .Replace("{PrimaryKey}", UpperFirstChar(primaryKeyName))
                .Replace("{TableName}", table.TableName);

            WriteAndSave(fullPath, content);

            Console.WriteLine("生成" + table.TableName + "Map!");
        }

        /// <summary>
        /// 生成属性
        /// </summary>
        /// <param name="column">列</param>
        /// <returns></returns>
        private string GenerateEntityProperty(DbTableColumn column)
        {
            var sb = new StringBuilder();
            if (!string.IsNullOrEmpty(column.Comment))
            {
                sb.AppendLine("\t\t/// <summary>");
                sb.AppendLine("\t\t/// " + column.Comment);
                sb.AppendLine("\t\t/// </summary>");
            }
            if (column.IsPrimaryKey)
            {
                sb.AppendLine($"\t\tpublic {column.CSharpType} {UpperFirstChar(column.ColName)} " + "{get;set;}");
            }
            else
            {
                if (!column.IsNullable)
                {
                    sb.AppendLine("\t\t[Required(ErrorMessage = \"请输入" + column.Comment + "\")]");
                }

                if (column.ColumnLength.HasValue && column.ColumnLength.Value > 0)
                {
                    sb.AppendLine($"\t\t[MaxLength({column.ColumnLength.Value})]");
                }

                var colType = column.CSharpType;
                if (colType.ToLower() != "string" && colType.ToLower() != "byte[]" && colType.ToLower() != "object" &&
                    column.IsNullable)
                {
                    colType = colType + "?";
                }

                sb.AppendLine($"\t\tpublic {colType} {UpperFirstChar(column.ColName)} " + "{get;set;}");
            }

            return sb.ToString();
        }

        #endregion

        #region 服务层

        /// <summary>
        /// 生成IService层代码文件
        /// </summary>
        /// <param name="table"></param>
        /// <param name="ifExsitedCovered"></param>
        private void GenerateIServices(DbTable table, bool ifExsitedCovered = true)
        {
            var iServicesPath = _options.OutputPath + Delimiter + "IServices";
            if (!Directory.Exists(iServicesPath))
            {
                Directory.CreateDirectory(iServicesPath);
            }
            var fullPath = iServicesPath + Delimiter + "I" + GetFileName(table.TableName) + ".cs";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;
            var content = ReadTemplate("IServicesTemplate.txt");
            content = content.Replace("{Comment}", table.TableComment)
                .Replace("{Author}", _options.Author)
                .Replace("{Comment}", table.TableComment)
                .Replace("{GeneratorTime}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                .Replace("{IServicesNamespace}", _options.IServicesNamespace)
                .Replace("{ModelName}", GetFileName(table.TableName));
            WriteAndSave(fullPath, content);

            Console.WriteLine("生成" + table.TableName + "IService!");
        }

        /// <summary>
        /// 生成Services层代码文件
        /// </summary>
        /// <param name="table"></param>
        /// <param name="ifExsitedCovered"></param>
        private void GenerateServices(DbTable table,  bool ifExsitedCovered = true)
        {
            var repositoryPath = _options.OutputPath + Delimiter + "Services";
            if (!Directory.Exists(repositoryPath))
            {
                Directory.CreateDirectory(repositoryPath);
            }
            var fullPath = repositoryPath + Delimiter + GetFileName(table.TableName) + ".cs";
            if (File.Exists(fullPath) && !ifExsitedCovered)
                return;
            var content = ReadTemplate("ServiceTemplate.txt");
            content = content.Replace("{Comment}", table.TableComment)
                .Replace("{Author}", _options.Author)
                .Replace("{Comment}", table.TableComment)
                .Replace("{GeneratorTime}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                .Replace("{ServicesNamespace}", _options.ServicesNamespace)
                .Replace("{ModelName}", GetFileName(table.TableName));
            WriteAndSave(fullPath, content);

            Console.WriteLine("生成" + table.TableName + "Services!");
        }

        #endregion

        #endregion

        #region 读写文件

        /// <summary>
        /// 从代码模板中读取内容
        /// </summary>
        /// <param name="templateName">模板名称，应包括文件扩展名称。比如：template.txt</param>
        /// <returns></returns>
        private string ReadTemplate(string templateName)
        {
            var content = string.Empty;

            //直接读取路径
            //string path = AppDomain.CurrentDomain.BaseDirectory;
            //string fullName = path + "CodeTemplate/" + templateName;
            //using (var reader = new StreamReader(fullName))
            //{
            //    content = reader.ReadToEnd();
            //}

            //获取包含当前正在执行的代码的程序集
            var currentAssembly = Assembly.GetExecutingAssembly();
            //获取到嵌入的资源文件
            using (var stream = currentAssembly.GetManifestResourceStream($"{currentAssembly.GetName().Name}.CodeTemplate.{templateName}"))
            {
                if (stream != null)
                {
                    using (var reader = new StreamReader(stream))
                    {
                        content = reader.ReadToEnd();
                    }
                }
            }
            return content;
        }

        /// <summary>
        /// 写文件
        /// </summary>
        /// <param name="fileName">文件完整路径</param>
        /// <param name="content">内容</param>
        private static void WriteAndSave(string fileName, string content)
        {
            //实例化一个文件流--->与写入文件相关联
            using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
            {
                //实例化一个StreamWriter-->与fs相关联
                using (var sw = new StreamWriter(fs))
                {
                    //开始写入
                    sw.Write(content);
                    //清空缓冲区
                    sw.Flush();
                    //关闭流
                    sw.Close();
                    fs.Close();
                }
            }
        }

        /// <summary>
        /// 将小驼峰字符串的第一个字符大写
        /// </summary>
        public string UpperFirstChar(string str)
        {
            if (string.IsNullOrEmpty(str) || !char.IsLower(str[0]))
            {
                return str;
            }
            if (str.Length == 1)
            {
                return char.ToUpper(str[0]).ToString();
            }
            return char.ToUpper(str[0]) + str.Substring(1, str.Length - 1);
        }

        /// <summary>
        /// 去表前缀，获取最终文件名
        /// </summary>
        /// <param name="tableName"></param>
        /// <returns></returns>
        public string GetFileName(string tableName)
        {
            string result = string.Empty;
            if (string.IsNullOrWhiteSpace(tableName))
            {
                result = "";
            }
            else
            {
                if (tableName.IndexOf('_') > 0)
                {
                    string s = tableName.Substring(0, tableName.IndexOf('_') + 1);
                    result = tableName.Replace(s, "");
                    result = UpperFirstChar(result);
                }
                else
                {
                    result = UpperFirstChar(tableName);
                }
            }
            return result;
        }

        #endregion
    }
}
